package chloroform

import _root_.util.logging.Logged
import _root_.util.{ParameterizedTypeWrapper, GenericAnalysisUtils, NamedFunction2}
import scaladuct._
import java.util.Locale
import java.util.concurrent.locks.ReentrantLock
import java.lang.{Class, String}
import collection.immutable.List
import javax.servlet.http._
import java.util.logging.Logger
import javax.servlet._
import util.{JsonRendererUtil, RequestUtils}
import java.lang.reflect.Type
import validation.ValidatingPage
import argo.jdom.{JsonNodeBuilder, JsonRootNode, JsonObjectNodeBuilder}

/**
 *
 * todo: The instance of this class should be in the session scope
 */
@serializable
@SerialVersionUID(1)
class DefaultConfigPageRegisterLookupFactory extends ConfigPageRegisterLookup with HttpSessionListener with Logged {

  val META_PROJECT_NAME = "metaProject"

  private val metaMetaRegister = new MetaMetaRegister
  private var metaRegister: MetaConfigPageRegister = null

  private var currentProjectName: String = null
  private var currentProjectRef: ProjectRef = null
  private var metaProjectRef: ProjectRef = null
  private var projectCache: ProjectCache = null
  private var resolver: ResourceResolver = null
  private var projectLoader: ProjectLoader = null

  def getResourceResolver() = resolver

  def getProjectLoader() = projectLoader

  def getProjectName() = currentProjectName

  def getMetaProjectName() = META_PROJECT_NAME

  def getProjectRef() = currentProjectRef

  def getMetaProjectRef() = metaProjectRef

  def getProjectCache() = projectCache

  def getScenarios() = {
    try {
      getProjectRef.getProject.getScenarios.toArray
    }
    catch {
      case t: Throwable => {
        warn(t.getMessage)
        Array.empty
      }
    }
  }

  def getMetaScenarios() = {
    try {
      getMetaProjectRef.getProject.getScenarios.toArray
    }
    catch {
      case t: Throwable => {
        warn(t.getMessage)
        Array.empty
      }
    }
  }

  def getMetaRegister() = metaRegister

  def getMetaMetaRegister() = metaMetaRegister

  private def resolveProjectName(request: HttpServletRequest) = {
    RequestUtils.resolveProjectName(request)
  }

  def getConfigPageRegister(projectName: String, request: HttpServletRequest) =
    WebConfigPageRegister(projectName, request.getSession, request.getLocale, metaRegister, projectCache)

  def getConfigPageRegister(request: HttpServletRequest): ConfigPageRegister =
    getConfigPageRegister(currentProjectName, request)

  def sessionCreated(ev: HttpSessionEvent) = {
    val session = ev.getSession
    session.setAttribute(classOf[ConfigPageRegisterLookup].getName, this)

    currentProjectName = session.getServletContext.getInitParameter("projectName")
    require(currentProjectName != null, "The projectName parameter is not specified in web.xml")

    // create the resource resolver
    val resolverClassName = session.getServletContext.getInitParameter("projectURLResolver")
    resolver = if (resolverClassName == null)
      new ClasspathResourceResolver()
    else {
      val resolverClass = Thread.currentThread.getContextClassLoader.loadClass(resolverClassName)
      resolverClass.newInstance.asInstanceOf[ResourceResolver]
    }

    projectLoader = new WebAppProjectLoader(resolver, this)
    projectCache = new ProjectCache(projectLoader, Locale.getDefault) // todo: find out the current user's locale
    currentProjectRef = new ProjectRef(currentProjectName, projectCache)
    metaProjectRef = new ProjectRef(META_PROJECT_NAME, projectCache)

    metaRegister = new ConfigPageRegisterImpl(META_PROJECT_NAME, metaMetaRegister, true, projectCache)
      with MetaConfigPageRegister

  }

  def sessionDestroyed(ev: HttpSessionEvent) = {
    // save the project
    currentProjectRef.getProject match {
      case savable: ProjectSaver => savable.save
      case _ =>
    }
  }

}

/**
 * Instances of this class are valid no longer than during processing a request.
 */
object WebConfigPageRegister {
  def apply(projectName: String, httpSession: HttpSession, locale: Locale, metaRegister: MetaConfigPageRegister,
            projectCache: ProjectCache) = {

    var pageReg: WebConfigPageRegister = null;

    httpSession.synchronized {
      val instanceResolver: InstanceResolver = {
        val resolver = httpSession.getAttribute(classOf[InstanceResolver].getName).
          asInstanceOf[InstanceResolver]
        if (resolver == null) {
          val res = new DefaultInstanceResolver(httpSession)
          //val res = new DummyInstanceResolver()
          httpSession.setAttribute(classOf[InstanceResolver].getName, res)
          res
        } else {
          resolver
        }
      }
      pageReg = new WebConfigPageRegister(projectName, instanceResolver, locale, metaRegister, projectCache)
    }

    pageReg
  }
}

class WebConfigPageRegister(projectName: String, instanceResolver: InstanceResolver, locale: Locale,
                            metaRegister: MetaConfigPageRegister, projectCache: ProjectCache)
  extends ConfigPageRegisterImpl(projectName, metaRegister, false, projectCache) {

  override protected def resolveInstanceForType(instanceType: Type) = {
    if (instanceResolver != null) {
      instanceResolver.resolveInstanceForType(instanceType) match {
        case None => super.resolveInstanceForType(instanceType)
        case Some(instance) => Some(instance)
      }
    } else instanceResolver.resolveInstanceForType(instanceType)
  }
}

@serializable
@SerialVersionUID(1L)
trait InstanceResolver {
  def resolveInstanceForType(instanceType: Type): Option[Any]
}

class DummyInstanceResolver extends InstanceResolver {
  def resolveInstanceForType(instanceType: Type): Option[Any] = None
}

object DefaultInstanceResolver {
  val logger = Logger.getLogger(classOf[DefaultInstanceResolver].getName)
}

/**
 * This filter sets the session to to the DefaultInstanceResolver. This is a workaround
 * of GAE's problem of not notifying about session passivation/activation.
 */
class DefaultInstanceResolverSessionSetter extends Filter {
  def init(cfg: FilterConfig) = {}

  def doFilter(req: ServletRequest, resp: ServletResponse, chain: FilterChain) = {
    val session = req.asInstanceOf[HttpServletRequest].getSession()
    session.getAttribute(classOf[InstanceResolver].getName) match {
      case resolver: DefaultInstanceResolver =>
        resolver.httpSession = session
      case _ =>
    }

    chain.doFilter(req, resp)
  }

  def destroy = {}

}

class DefaultInstanceResolver(@transient var httpSession: HttpSession)
  extends InstanceResolver with HttpSessionActivationListener with Logged {

  //  private def writeObject(out: java.io.ObjectOutputStream) {
  //    DefaultInstanceResolver.logger.info("serializing DefaultInstanceResolver")
  //    out.defaultWriteObject
  //  }
  //
  //  private def readObject(in: java.io.ObjectInputStream) {
  //    in.defaultReadObject
  //    DefaultInstanceResolver.logger.info("deserializing DefaultInstanceResolver")
  //  }

  def sessionDidActivate(ev: HttpSessionEvent) = {
    httpSession = ev.getSession
    DefaultInstanceResolver.logger.info("sessionDidActivate: " + ev.getSession)
  }

  def sessionWillPassivate(ev: HttpSessionEvent) = {
    DefaultInstanceResolver.logger.info("session WillPassivate: " + ev.getSession)
  }

  def resolveInstanceForType(instanceType: Type) = {
    if (instanceType.isInstanceOf[Class[_]]) {
      val instanceClass = instanceType.asInstanceOf[Class[_]]
      var instance = httpSession.getAttribute(instanceClass.getName)
      if (instance == null) {
        instance = httpSession.getServletContext.getAttribute(instanceClass.getName)
      }

      if (instance == null)
        None
      else
        Some(instance)

    } else None
  }
}

@serializable
@SerialVersionUID(1L)
trait ConfigPageRegister extends LookupFunctions.LookupFunction {

  def getConfigPageNames(): List[String]

  def getConfigPage[X](pageName: String): ConfigPage[X] = {
    getConfigPage(pageName, true, false, null, Nil, true)
  }

  def getConfigPage[X](pageName: String, faultTolerant: Boolean, constructJsonBuilders: Boolean,
                       pageHandler: (ConfigPage[X]) => ConfigPage[X],
                       additionalCookies: List[ProjectCookie],
                       includeExtensions: Boolean,
                       pageConstructorArgs: AnyRef*): ConfigPage[X]

  def getStarter[X](pageName: String, pageHandler: (ConfigPage[X]) => ConfigPage[X] = null,
                    additionalCookies: List[ProjectCookie] = null): Starter[X]


  def getConfigPage(pagePath: Array[String]): ConfigPage[_] = {
    val pageName = pagePath.head

    val pathTail: Array[String] = pagePath.tail
    if (pathTail.length == 0) {
      getConfigPage(pageName, true, false, null, Nil, false)
    } else {
      val extPage = getConfigPage(pathTail)
      val extPath = pagePath.reduceLeft((agg, step) => {
        step + "," + agg
      })
      getConfigPage(extPath, true, false, null, Nil, false, extPage)
    }

  }


}

trait MetaConfigPageRegister extends ConfigPageRegister {

  //  def getStarter[X](metaPageName: String, pageName: String): Starter[X] = {
  //    getStarter(metaPageName, (firstMetaPage) => {
  //      Extensible.extension(firstMetaPage, classOf[NeedsPageName[X]]) match {
  //        case None =>
  //        case Some(needsPageName) => needsPageName.setPageName(pageName)
  //      }
  //      firstMetaPage
  //    })
  //  }

}

@SerialVersionUID(1L)
class ConfigPageRegisterImpl(val projectName: String, val metaRegister: MetaConfigPageRegister,
                             val cacheStarters: Boolean, val projectCache: ProjectCache)
  extends ConfigPageRegister with Logged {

  private var starters: Map[String, Starter[_]] = Map()
  private val startersLock = new ReentrantLock

  def apply(query: AnyRef): List[(Type, AnyRef *) => ConfigPage[_]] = getPageFunction(query)

  def getConfigPageNames() = {
    projectCache.getProject(projectName).getEntryNames
  }

  private def getPageFunction[X](query: AnyRef): List[(Type, AnyRef *) => ConfigPage[X]] = {
    if (query.isInstanceOf[RefInfo]) {
      resolvePageFnQuery(query.asInstanceOf[RefInfo])
    } else {
      resolveSinglePageFn(query.toString, false)
    }
  }

  private def resolveSinglePageFn[X](pageName: String, constructJsonBuilders: Boolean): List[(Type, AnyRef *) => ConfigPage[X]] = {
    val singletonPageFnList = starters.get(pageName) match {
      case None => resolveLookup[X](pageName, false, constructJsonBuilders, null, Nil, true)
      case Some(starter) => {
        // return the stored starter disguised as a terminal page
        val pageFn: (Type, AnyRef *) => ConfigPage[X] = (resultType, args) => {
          new FinishedConfigPage[X](pageName, starter.asInstanceOf[Starter[X]])
        }
        List(pageFn)
      }
    }
    singletonPageFnList
  }

  private def filterCompatibleEntries(refInfo: RefInfo)(entry: ProjectEntry): Boolean = {
    val entryType = resolveProductType(entry)
    if (entryType == null)
      false
    else {
      GenericAnalysisUtils.isCompatible(entryType, refInfo.getType)
    }
  }

  /**
   * Returns a list of config page functions (Class[_], AnyRef *) => ConfigPage[X]). Each page function corresponds to
   * one project entry whose name matches the query (refInfo).
   */
  def resolvePageFnQuery[X](refInfo: RefInfo): List[(Type, AnyRef *) => ConfigPage[X]] = {
    val project = projectCache.getProject(projectName)
    // Map all matching project entries to page functions.
    val pageFnList: List[LookupFunctions.PageFunction] = project.getEntries.filter(filterCompatibleEntries(refInfo)).map(entry => {
      val entryName = entry.name

      // Each page function is a closure that "remembers' its entry name (entryName). When invoked it calls the apply method
      // on the registry (ConfigPageRegisterImpl) passing the entry name as the argument. A list containing only one
      // result is expected.
      val pageFnForEntry: LookupFunctions.PageFunction = (cls, args) => {
        val listOfPageFnForEntry: List[(Type, AnyRef *) => ConfigPage[_]] = apply(entryName)
        val cfgPage: ConfigPage[_] = listOfPageFnForEntry match {
          case List(entryCfgPage) => entryCfgPage(cls, args)
          case _ => error("Too many or 0 entries for page " + entryName)
        }
        cfgPage
      }
      val namedPageFnForEntry: LookupFunctions.PageFunction = new NamedFunction2(entryName,
        pageFnForEntry.asInstanceOf[Function2[Type, Seq[AnyRef], ConfigPage[_]]])
      namedPageFnForEntry
    })
    pageFnList.asInstanceOf[List[(Type, AnyRef *) => ConfigPage[X]]]
  }

  private def resolveLookup[X](pageName: String, faultTolerant: Boolean, constructJsonBuilders: Boolean,
                               pageHandler: (ConfigPage[X]) => ConfigPage[X],
                               additionalCookies: List[ProjectCookie],
                               includeExtensions: Boolean):
  List[(Type, AnyRef *) => ConfigPage[X]] = {

    if (pageName == null || "" == pageName || "?" == pageName)
      resolveAnonymousPage(pageHandler)
    else
      resolveNamedPage(pageName, faultTolerant, constructJsonBuilders, pageHandler, additionalCookies, includeExtensions)
  }

  private def resolveAnonymousPage[X](pageHandler: (ConfigPage[X]) => ConfigPage[X] = null):
  List[(Type, AnyRef *) => ConfigPage[X]] = {

    val pageFn: (Type, AnyRef *) => ConfigPage[X] = (instanceType, args) => {

      resolveInstanceForType(instanceType) match {
        case None => throw new IllegalArgumentException("No page found for type " + GenericAnalysisUtils.toString(instanceType))
        case Some(instance) => {
          new FinishedConfigPage[X](GenericAnalysisUtils.getTypeName(instanceType), new BasicStarter[X](instance.asInstanceOf[X]))
        }
      }
    }

    List(pageFn)
  }

  protected def resolveInstanceForType(instanceType: Type): Option[Any] = {
    if (GenericAnalysisUtils.isCompatible(classOf[LookupFunctions.LookupFunction], instanceType)) {
      Some(this)
    } else if (GenericAnalysisUtils.isCompatible(classOf[ConfigPageRegister], instanceType)) {
      Some(this)
    } else if (GenericAnalysisUtils.isCompatible(classOf[MetaConfigPageRegister], instanceType)) {
      Some(metaRegister)
    } else if (GenericAnalysisUtils.isCompatible(classOf[Project], instanceType)) {
      Some(projectCache.getProject(projectName))
    } else if (GenericAnalysisUtils.isCompatible(HttpServletRequestGetterType, instanceType)) {
      Some(() => RequestCache.getRequest)
    } else if (GenericAnalysisUtils.isCompatible(HttpServletResponseGetterType, instanceType)) {
      Some(() => RequestCache.getResponse)
    } else None
    // todo:
  }

  protected def resolveProductType(entry: ProjectEntry): Type = {
    entry.asInstanceOf[TypedProjectEntry].getEntryType
    //    val factoryStarter: Starter[ConfigPage[_]] = metaRegister.getStarter(entry.factory)
    //    Extensible.extension(factoryStarter, classOf[HasProductType]) match {
    //      case Some(hasPT) => {
    //        // Take the first and unique type parameter as both ConfigPage and Starter types have just one parameter.
    //        hasPT.getProductType().asInstanceOf[ParameterizedType].getActualTypeArguments()(0)
    //      }
    //      case None => null
    //    }
  }

  protected def resolveNamedPage[X](pageName: String, faultTolerant: Boolean, constructJsonBuilders: Boolean,
                                    pageHandler: (ConfigPage[X]) => ConfigPage[X] = null,
                                    additionalCookies: List[ProjectCookie],
                                    includeExtensions: Boolean):
  List[(Type, AnyRef *) => ConfigPage[X]] = {

    val project = projectCache.getProject(projectName)

    // Parse the pageName that may represent a path to an extending entry.
    // The steps in the path are delimited by dots
    var entry: ProjectEntry = project.findInPath(pageName)

    resolveProjectEntry(entry, faultTolerant, constructJsonBuilders, pageHandler, additionalCookies, includeExtensions)
  }

  protected def resolveProjectEntry[X](storedPg: ProjectEntry, faultTolerant: Boolean,
                                       constructJsonBuilders: Boolean,
                                       pageHandler: (ConfigPage[X]) => ConfigPage[X] = null,
                                       additionalCookies: List[ProjectCookie],
                                       includeExtensions: Boolean):
  List[(Type, AnyRef *) => ConfigPage[X]] = {

    val pageFn: (Type, AnyRef *) => ConfigPage[X] = (resultClass, args) => {

      val factoryStarter: Starter[ConfigPageFactory[X]] = metaRegister.getStarter(storedPg.factory)

      val pageName = storedPg.fullName
      //      val pageName = if (parentName == null)
      //        storedPg.name
      //      else
      //        parentName + "." + storedPg.name
      //
      val origPage = factoryStarter.newInstance().createConfigPage(pageName, this, args: _*)
      val newPage: ConfigPage[X] = if (pageHandler != null) {
        pageHandler(origPage)
      } else origPage

      /**
       * This wrapper makes sure that the cookies are injected with required context values and then validated.
       */
      class ConfigPageAdapter(wrappedPage: ConfigPage[X]) extends ConfigPageDecorator[X](wrappedPage)
      with ValidatingPage[X] with InjectingPage[X] {

        val lookupFunction = ConfigPageRegisterImpl.this

        override def consume(cookie: decoratedPage.Cookie) = super.consume(cookie) match {
          case NewStarter(starter) => {
            if (cacheStarters)
              starters += (pageName -> starter)
            NewStarter(starter)
          }
          case NextPage(nextPage) => NextPage(new ConfigPageAdapter(nextPage))
        }
      }

      var extendedPage: ConfigPage[X] = new ConfigPageAdapter(newPage)
      // Extend the page by the explicit extensions (decorators) before feeding it by the project (persistent) cookies
      val exts: List[ProjectEntry] = storedPg.extensions
      if (includeExtensions && exts != null)
        exts.foreach(ext => {
          val pageFns: List[(Type, AnyRef *) => ConfigPage[X]] =
            resolveProjectEntry[X](ext, faultTolerant, constructJsonBuilders, pageHandler, Nil, includeExtensions)
          require(pageFns.size == 1)
          val extPage: ConfigPage[X] = pageFns.head.apply(resultClass, extendedPage /*constructor arg*/)
          extendedPage = extPage
        })

      val (resPage, updatedCookieBuilders): (ConfigResult[X], List[JsonObjectNodeBuilder]) =
        feedPage(extendedPage, storedPg.cookies ::: additionalCookies, faultTolerant, constructJsonBuilders)

      if (constructJsonBuilders)
        new ConfigPageDecorator[X](resPage match {
          case NextPage(nextPage) => nextPage
          case NewStarter(newStarter) => new FinishedConfigPage(pageName, newStarter)
        }) with PreConfiguredPage[X] {
          def getJsonCookieBuilders = updatedCookieBuilders
        }
      else {
        new ConfigPageDecorator[X](resPage match {
          case NextPage(nextPage) => nextPage
          case NewStarter(newStarter) => new FinishedConfigPage(pageName, newStarter)
        })
      }
    }

    List(pageFn)
  }

  def getStarter[X](pageName: String, pageHandler: (ConfigPage[X]) => ConfigPage[X] = null,
                    additionalCookies: List[ProjectCookie] = Nil): Starter[X] = {
    var starter: Starter[X] = null
    startersLock.lock
    try {
      starter = starters.get(pageName) match {
        case Some(starter) => starter.asInstanceOf[Starter[X]]
        case None => {
          val page: ConfigPage[X] = getConfigPage(pageName, false, false, pageHandler, additionalCookies, true)
          val cookie = page.getCookie

          def consumeNonInteractiveCookies(p: ConfigPage[X]): Starter[X] = {
            if (cookie == Terminal || PropertyUtils.containsContextReferencesOnly(cookie)) {
              p.consume(p.getCookie) match {
                case NextPage(anotherPage) => consumeNonInteractiveCookies(anotherPage)
                case NewStarter(s) => s.asInstanceOf[Starter[X]]
              }
            } else {
              throw new IllegalArgumentException("Config page " + pageName + " is not completely pre-configured")
            }
          }

          //starters += (pageName -> starter)
          consumeNonInteractiveCookies(page)
        }
      }
    }
    finally {
      startersLock.unlock
    }

    starter
  }

  private def feedPage[X](page: ConfigPage[X], cookies: List[ProjectCookie], faultTolerant: Boolean,
                          constructJsonBuilders: Boolean):

  (ConfigResult[X], List[JsonNodeBuilder[JsonRootNode]]) = {

    if (!cookies.isEmpty) {
      val configSession = new StarterConfigMessageHandler(page, Locale.getDefault, this)

      // Consume the JSON cookies found in the project file. The result is a list containing JSON builders for
      // every cookie updated during the call of the consumeJsonNodes method.
      val updatedCookieBuilders: List[JsonNodeBuilder[JsonRootNode]] = try {
        //val cookiesAsListOfMaps: List[java.util.Map[JsonStringNode, JsonNode]] = cookies.map(_.properties)
        //configSession.consumeJsonNodes(cookiesAsListOfMaps, constructJsonBuilders)
        configSession.consumeJsonNodes(cookies, constructJsonBuilders)
      } catch {
        case e: IncompleteCookiesConsumption => {
          if (faultTolerant) {
            if (!e.cookieBuilders.isEmpty) {
              val problematicConfig = JsonRendererUtil.renderToString(e.cookieBuilders.last)
              warn("Error in persistent configuration cookie for page " + page.id + ". The problematic cookie " + problematicConfig, e)
            } else {
              warn("Error in persistent configuration cookie for page " + page.id, e)
            }
            e.cookieBuilders
          } else throw e
        }
      }

      val lastPage: ConfigPage[Any] = configSession.cookieGateway.getCurrentPage
      if (lastPage == null)
        (NewStarter(configSession.cookieGateway.getStarter.asInstanceOf[Starter[X]]), updatedCookieBuilders)
      else
        (NextPage(lastPage.asInstanceOf[ConfigPage[X]]), updatedCookieBuilders)
    } else (NextPage(page), Nil)
  }

  def getConfigPage[X](pageName: String, faultTolerant: Boolean, constructJsonBuilders: Boolean,
                       pageHandler: (ConfigPage[X]) => ConfigPage[X],
                       additionalCookies: List[ProjectCookie],
                       includeExtensions: Boolean,
                       pageConstructorArgs: AnyRef*) =

    resolveLookup(pageName, faultTolerant, constructJsonBuilders, pageHandler, additionalCookies, includeExtensions) match {
      case Nil => throw new IllegalArgumentException("Unknown config page " + pageName)
      case List(pageFn) => pageFn(classOf[Any], pageConstructorArgs: _*).asInstanceOf[ConfigPage[X]]
    }

}

@SerialVersionUID(1L)
class MetaMetaRegister extends ConfigPageRegister with MetaConfigPageRegister {

  val getConfigPageNames = List(classOf[ClasspathConfigPage[_]].getName)

  def getConfigPage[X](metaMetaPageName: String, faultTolerant: Boolean, constructJsonBuilders: Boolean,
                       pageHandler: (ConfigPage[X]) => ConfigPage[X], additionalCookies: List[ProjectCookie],
                       includeExtensions: Boolean,
                       pageConstructorArgs: AnyRef*) = {
    if (metaMetaPageName == classOf[ClasspathConfigPage[_]].getName) {
      new ClasspathConfigPageFactory[Any]().asInstanceOf[ConfigPage[X]]
      //    } else if (pageName == classOf[RemoteConfigPage].getName) {
      //      // todo
    } else
      throw new IllegalArgumentException("Meta-meta config page " + metaMetaPageName + " not found")
  }

  def getStarter[X](pageName: String, pageHandler: (ConfigPage[X]) => ConfigPage[X] = null,
                    additionalCookies: List[ProjectCookie] = Nil) = {
    val page: ConfigPage[Nothing] = getConfigPage(pageName, false, false, null, Nil, true)
    page.consume(page.getCookie) match {
      case NextPage(p) => error("not expected")
      case NewStarter(starter) => starter.asInstanceOf[Starter[X]]
    }
  }

  def apply(pageName: AnyRef): List[(Type, AnyRef *) => ConfigPage[_]] = {
    val page = getConfigPage(pageName.toString, false, false, null, Nil, true)
    val pageFn: (Type, AnyRef *) => ConfigPage[_] = (resultClass, args) => {
      page
    }
    List(pageFn)
  }
}

trait TypedProjectEntry extends ProjectEntry {
  def getEntryType(): Type

  def reloadEntryType(): Type
}

@SerialVersionUID(1)
class WebAppProjectLoader(resolver: ResourceResolver, app: ConfigPageRegisterLookup) extends JsonProjectLoader(resolver) {
  override protected def createJsonProject(projectName: String, projectResource: Resource, projectModel: JsonRootNode) = {
    @SerialVersionUID(1)
    class WebAppJsonProject extends JsonProject(projectName, projectResource, projectModel) {

      def getRegister: ConfigPageRegister = {
        if (projectName == app.getMetaProjectName)
          app.getMetaRegister
        else
          app.getConfigPageRegister(RequestCache())
      }

      /**
       * This overridden factory method creates entries that are capable of giving information about the type of
       * the product produced by the associated config page or starter.
       */
      override def createProjectEntry(packagedArgs: ProjectEntry) = {

        @SerialVersionUID(1)
        class WebAppProjectEntry extends ProjectEntry(packagedArgs) with InnerProjectEntry with TypedProjectEntry {

          @transient
          private[this] var entryType: Type = null

          def reloadEntryType() = {
            this.synchronized {
              def getRootEntry(entry: ProjectEntry): ProjectEntry = {
                if (entry.extendedEntry == null)
                  entry
                else
                  getRootEntry(entry.extendedEntry)
              }

              val rootEntry = getRootEntry(this)

              val entryName = rootEntry.name
              val page = getRegister.getConfigPage(entryName)
              entryType = page.extension(classOf[HasProductType]) match {
                case None => classOf[Any]
                case Some(productType) => productType.getProductType
              }
            }
            entryType
          }

          def getEntryType() = {
            this.synchronized {
              if (entryType == null)
                reloadEntryType
            }
            entryType
          }
        }

        new WebAppProjectEntry
      }

      override def verifyConsistency(entry: EntryType): Option[ConsistencyViolation] = {
        try {
          //val projectName = ProjectRef(RequestCache().getSession).projectName
          //ConfigPageRegisterLookup.getConfigPageRegister(RequestCache()).getConfigPage(entry.name, false)
          val register = getRegister

          register.getConfigPage(entry.name, false, false, null, Nil, true)

          None
        }
        catch {
          case t: Throwable => {
            warn(t)
            Some(ConsistencyViolation(t))
          }
        }
      }
    }

    new WebAppJsonProject
  }
}

object HttpServletRequestGetterType
  extends ParameterizedTypeWrapper(null, classOf[Function0[_]], Array(classOf[HttpServletRequest]))

object HttpServletResponseGetterType
  extends ParameterizedTypeWrapper(null, classOf[Function0[_]], Array(classOf[HttpServletResponse]))

